/**
 ************************************************************************************************
 * 												wavio											*
 ************************************************************************************************
 * 
 * A set of functions for reading and writing Microsoft .WAV files. Wraps stdio.h
 * 
 * Copyright (c) 2014, Aaron L. Jones
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted 
 * provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 
 * conditions and the following disclaimer in the documentation and/or other materials provided 
 * with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
**/

#include "wavio.h"

wav_file* wav_fopen(const char *filename, const wav_mode *mode)
{
	wav_file* stream;
	
	/* allocate memory for the streaming structure */
	stream = (wav_file*) malloc(sizeof(*stream));
	/* initialize to "no error" */
	stream->flags = 0;
	
	/* "mode" parameter is set to NULL for reading */
	if(!mode)
	{
		/* use "id" strings to check file format */
		char chunk_id[5];	
		chunk_id[4] = '\0';
		
		/* write flag off */
		stream->flags &= (!WAV_WRITE_FLAG);
		
		/* open wav file */
		stream->file = fopen(filename,"rb");
		if(!stream->file)
		{
			fprintf(stderr, "Error reading .WAV file: unable to open file: %s\n", filename);
			free(stream);
			return(NULL);
		}
		
		/* check file format */
		fread(chunk_id, 1, 4, stream->file);
		if(!strcmp(chunk_id, "RIFF"))
		{
			/* if correct format, read chunk size */
			fread((char*) &stream->head.chunk_size, sizeof(stream->head.chunk_size), 1, stream->file);
		}
		else
		{
			fprintf(stderr, "Error reading .WAV file: unexpected chunk id: %s\n", chunk_id);
			free(stream);
			return(NULL);
		}
		
		fread(chunk_id, 1, 4, stream->file);
		if(!strcmp(chunk_id, "WAVE"))
		{
			fread(chunk_id, 1, 4, stream->file);
			if(!strcmp(chunk_id, "fmt ")){
				/* read audio format information */
				fread((char*) &stream->head.subchunk1_size, sizeof(stream->head.subchunk1_size), 1, stream->file);
				fread((char*) &stream->head.audio_format, sizeof(stream->head.audio_format), 1, stream->file);
				fread((char*) &stream->head.channelct, sizeof(stream->head.audio_format), 1, stream->file);
				fread((char*) &stream->head.sample_rate, sizeof(stream->head.sample_rate), 1, stream->file);
				fread((char*) &stream->head.byte_rate, sizeof(stream->head.byte_rate), 1, stream->file);
				fread((char*) &stream->head.block_align, sizeof(stream->head.block_align), 1, stream->file);
				fread((char*) &stream->head.bit_depth, sizeof(stream->head.bit_depth), 1, stream->file);
			}
			else
			{
				fprintf(stderr, "Error reading .WAV file: unexpected subchunk 1 id: %s\n", chunk_id);
				free(stream);
				return(NULL);
			}
			
			/** 
			 * check for fact chunk
			 * @Todo: what the heck is the fact chunk for? do we need to extract any information from here?
			**/
			fseek(stream->file, stream->head.subchunk1_size + 20, SEEK_SET);
			fread(chunk_id, 1, 4, stream->file);
			if(!strcmp(chunk_id, "fact")){
				uint32_t fact_subchunk_size;
				fread((char*) &fact_subchunk_size, sizeof(fact_subchunk_size), 1, stream->file);
				/* right now we ignore the fact chunk and go straight to the audio data */
				fseek(stream->file, stream->head.subchunk1_size + fact_subchunk_size + 28, SEEK_SET);
			}
			else fseek(stream->file, stream->head.subchunk1_size + 20, SEEK_SET);
			
			fread(chunk_id, 1, 4, stream->file);
			if(!strcmp(chunk_id, "data")){
				fread((char*) &stream->head.subchunk2_size, sizeof(stream->head.subchunk2_size), 1, stream->file);
			}
			else
			{
				fprintf(stderr, "Error reading .WAV file: unexpected subchunk 2 id: %s\n", chunk_id);
				free(stream);
				return(NULL);
			}
		}
		else
		{
			fprintf(stderr, "Error reading .WAV file: unexpected file format: %s\n", chunk_id);
			free(stream);
			return(NULL);
		}
	}
	/* "mode" parameters must be set to valid numbers to write a create a usable WAV file */
	else{									
		/* set flag to indicate writing */
		stream->flags = WAV_WRITE_FLAG;
		
		stream->file = fopen(filename,"wb");
		if(!stream->file)
		{
			free(stream);
			fprintf(stderr, "Error reading .WAV file: unable to open file: %s\n", filename);
			return(NULL);
		}
		
		stream->head.chunk_size = WAV_DEFAULT_CHUNK_SIZE;
		stream->head.subchunk1_size = WAV_DEFAULT_SUBCHUNK1_SIZE;
		stream->head.audio_format = WAV_DEFAULT_AUDIO_FORMAT;
		stream->head.channelct = mode->channelct;
		stream->head.sample_rate = mode->sample_rate;
		stream->head.byte_rate = mode->sample_rate * mode->channelct * mode->bit_depth >> 3;
		stream->head.block_align = mode->channelct * mode->bit_depth >> 3;
		stream->head.bit_depth = mode->bit_depth;
		stream->head.subchunk2_size = WAV_DEFAULT_SUBCHUNK2_SIZE;
		
		fwrite("RIFF", 1, 4, stream->file);
		fwrite((char*) &stream->head.chunk_size, sizeof(stream->head.chunk_size), 1, stream->file);
		fwrite("WAVE", 1, 4, stream->file);
		fwrite("fmt ", 1, 4, stream->file);
		fwrite((char*) &stream->head.subchunk1_size, sizeof(stream->head.subchunk1_size), 1, stream->file);
		fwrite((char*) &stream->head.audio_format, sizeof(stream->head.audio_format), 1, stream->file);
		fwrite((char*) &stream->head.channelct, sizeof(stream->head.audio_format), 1, stream->file);
		fwrite((char*) &stream->head.sample_rate, sizeof(stream->head.sample_rate), 1, stream->file);
		fwrite((char*) &stream->head.byte_rate, sizeof(stream->head.byte_rate), 1, stream->file);
		fwrite((char*) &stream->head.block_align, sizeof(stream->head.block_align), 1, stream->file);
		fwrite((char*) &stream->head.bit_depth, sizeof(stream->head.bit_depth), 1, stream->file);
		fwrite("data", 1, 4, stream->file);
		fwrite((char*) &stream->head.subchunk2_size, sizeof(stream->head.subchunk2_size), 1, stream->file);
	}
	
	/*
	printf("%s :\n", filename);
	printf("\tchunk size:\t%lu bytes\n", stream->head.chunk_size);
	printf("\tsubchunk1 size:\t%lu  bytes\n", stream->head.subchunk1_size);
	printf("\taudio format:\t%hu\n", stream->head.audio_format);
	printf("\tchannel count:\t%hu\n", stream->head.channelct);
	printf("\tsample rate:\t%lu Hz\n", stream->head.sample_rate);
	printf("\tdata rate:\t%lu bytes/sec\n", stream->head.byte_rate);
	printf("\tblock align:\t%hu bytes\n", stream->head.block_align);
	printf("\tresolution:\t%hu-bit\n", stream->head.bit_depth);
	printf("\tsubchunk2 size:\t%lu bytes\n", stream->head.subchunk2_size);
	printf("\n");
	*/
	
	
	return(stream);
}

int wav_fwrite(const void *ptr, uint32_t framect, wav_file *stream)
{
	unsigned long written;
	
	if(!stream) return(WAV_INVALID_STREAM);
	
	/* write new data to file */
	written = fwrite(ptr, stream->head.block_align, framect, stream->file);
	
	/* increment chunk sizes */
	stream->head.chunk_size += (stream->head.block_align * written);
	stream->head.subchunk2_size += (stream->head.block_align * written);
	
	/* check for errors in the stream */
	if(written != framect)
	{
		if(ferror(stream->file)) stream->flags |= WAV_ERROR_FLAG;
	}
	
	return(written);
}

int wav_fread(void* ptr, uint32_t framect, wav_file *stream)
{
	unsigned long read;
	
	if(!stream) return(WAV_INVALID_STREAM);
	
	/* read data from the file */
	read = fread(ptr, stream->head.block_align, framect, stream->file);
	
	/* check for errors or eof */
	if(read != framect)
	{
		if(ferror(stream->file))	stream->flags |= WAV_ERROR_FLAG;
		else if(feof(stream->file))	stream->flags |= WAV_EOF_FLAG;
	}
	
	return(read);
}

int wav_fclose(wav_file *stream)
{
	if(stream)
	{
		/*
		fseek(stream->file, 0, SEEK_SET);
		fwrite("RIFF", 1, 4, stream->file);
		fwrite((char*) &stream->head.chunk_size, sizeof(stream->head.chunk_size), 1, stream->file);
		fwrite("WAVE", 1, 4, stream->file);
		fwrite("fmt ", 1, 4, stream->file);
		fwrite((char*) &stream->head.subchunk1_size, sizeof(stream->head.subchunk1_size), 1, stream->file);
		fwrite((char*) &stream->head.audio_format, sizeof(stream->head.audio_format), 1, stream->file);
		fwrite((char*) &stream->head.channelct, sizeof(stream->head.audio_format), 1, stream->file);
		fwrite((char*) &stream->head.sample_rate, sizeof(stream->head.sample_rate), 1, stream->file);
		fwrite((char*) &stream->head.byte_rate, sizeof(stream->head.byte_rate), 1, stream->file);
		fwrite((char*) &stream->head.block_align, sizeof(stream->head.block_align), 1, stream->file);
		fwrite((char*) &stream->head.bit_depth, sizeof(stream->head.bit_depth), 1, stream->file);
		fwrite("data", 1, 4, stream->file);
		fwrite((char*) &stream->head.subchunk2_size, sizeof(stream->head.subchunk2_size), 1, stream->file);
		*/
		
		if(stream->flags & WAV_WRITE_FLAG){
			fseek(stream->file, 4, SEEK_SET);
			fwrite((char*) &stream->head.chunk_size, sizeof(stream->head.chunk_size), 1, stream->file);
			fseek(stream->file, stream->head.subchunk1_size + 24, SEEK_SET);
			fwrite((char*) &stream->head.subchunk2_size, sizeof(stream->head.subchunk2_size), 1, stream->file);
			fseek(stream->file, 0, SEEK_END);
		}
		
		fclose(stream->file);
		free(stream);
	}
	else return(WAV_INVALID_STREAM);
	return(0);
}

int wav_feof(wav_file *stream)
{
	if(!stream) return(WAV_INVALID_STREAM);
	else if(stream->flags & WAV_EOF_FLAG) return(WAV_EOF_FLAG);
	return(0);
}

uint32_t wav_get_sample_rate(wav_file *stream)
{
	if(!stream) return(WAV_INVALID_STREAM);
	return(stream->head.sample_rate);
}

uint16_t wav_get_bit_depth(wav_file *stream)
{
	if(!stream) return(WAV_INVALID_STREAM);
	return(stream->head.bit_depth);
}

uint16_t wav_get_channelct(wav_file *stream)
{
	if(!stream) return(WAV_INVALID_STREAM);
	return(stream->head.channelct);
}

uint16_t wav_get_block_align(wav_file *stream)
{
	if(!stream) return(WAV_INVALID_STREAM);
	return(stream->head.block_align);
}

double wav_get_duration(wav_file *stream)
{
	if(!stream) return(WAV_INVALID_STREAM);
	return( ((double) stream->head.subchunk2_size) / stream->head.byte_rate );
}
